package cn.sunline.cas.authentication;

import cn.sunline.cas.util.AESUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.adaptors.jdbc.QueryAndEncodeDatabaseAuthenticationHandler;
import org.apereo.cas.authentication.AuthenticationHandlerExecutionResult;
import org.apereo.cas.authentication.PreventedException;
import org.apereo.cas.authentication.UsernamePasswordCredential;
import org.apereo.cas.authentication.exceptions.AccountDisabledException;
import org.apereo.cas.authentication.exceptions.AccountPasswordMustChangeException;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.services.ServicesManager;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;

import javax.security.auth.login.AccountNotFoundException;
import javax.security.auth.login.FailedLoginException;
import javax.sql.DataSource;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Map;

@Slf4j
public class MyAuthenticationHandler extends QueryAndEncodeDatabaseAuthenticationHandler {

    private final String aesEntryKey;
    private final String aesVi;


    public MyAuthenticationHandler(String name, ServicesManager servicesManager, PrincipalFactory principalFactory,
                                   Integer order, DataSource dataSource, String algorithmName, String sql,
                                   String passwordFieldName, String expiredFieldName, String disabledFieldName,
                                   String aesEntryKey, String aesVi) {
        super(name, servicesManager, principalFactory, order, dataSource, algorithmName, sql,
                passwordFieldName, "salt", expiredFieldName, disabledFieldName,
                null, 1, "sunline");
        this.aesEntryKey = aesEntryKey;
        this.aesVi = aesVi;

    }

    @Override
    protected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInternal(UsernamePasswordCredential transformedCredential, String originalPassword) throws GeneralSecurityException, PreventedException {
        // 如果没有就调用父类
        if (StringUtils.isBlank(aesEntryKey)) {
            return super.authenticateUsernamePasswordInternal(transformedCredential, originalPassword);
        }

        if (StringUtils.isBlank(this.sql) || StringUtils.isBlank(this.algorithmName) || getJdbcTemplate() == null) {
            throw new GeneralSecurityException("Authentication handler is not configured correctly");
        }

        final String username = transformedCredential.getUsername();
        try {
            final Map<String, Object> values = getJdbcTemplate().queryForMap(this.sql, username);

            // 解析凭证记录中的密码和盐值
            String decryptPassword = AESUtil.decrypt(transformedCredential.getPassword(), this.aesEntryKey, this.aesVi);

            final String digestedPassword = digestEncodedPassword(decryptPassword, values);

            if (!values.get(this.passwordFieldName).equals(digestedPassword)) {
                throw new FailedLoginException("Password does not match value on record.");
            }
            if (StringUtils.isNotBlank(this.expiredFieldName) && values.containsKey(this.expiredFieldName)) {
                final String dbExpired = values.get(this.expiredFieldName).toString();
                if (BooleanUtils.toBoolean(dbExpired) || "1".equals(dbExpired)) {
                    throw new AccountPasswordMustChangeException("Password has expired");
                }
            }
            if (StringUtils.isNotBlank(this.disabledFieldName) && values.containsKey(this.disabledFieldName)) {
                final String dbDisabled = values.get(this.disabledFieldName).toString();
                if (BooleanUtils.toBoolean(dbDisabled) || "1".equals(dbDisabled)) {
                    throw new AccountDisabledException("Account has been disabled");
                }
            }
            return createHandlerResult(transformedCredential, this.principalFactory.createPrincipal(username), new ArrayList<>(0));

        } catch (final IncorrectResultSizeDataAccessException e) {
            if (e.getActualSize() == 0) {
                throw new AccountNotFoundException(username + " not found with SQL query");
            }
            throw new FailedLoginException("Multiple records found for " + username);
        } catch (final DataAccessException e) {
            throw new PreventedException("SQL exception while executing query for " + username, e);
        } catch (UnsupportedEncodingException e) {
            throw new PreventedException("AES Password Decrypt exception for " + transformedCredential.getPassword(), e);
        }
    }
}
